package com.gitee.ixtf.mongo.guice

import com.gitee.ixtf.core.kotlinx.IxtfEnvLong
import com.gitee.ixtf.mongo.Jmongo
import com.gitee.ixtf.mongo.MongoEntityBase
import com.gitee.ixtf.mongo.codec.IOperatorCodecProvider
import com.gitee.ixtf.mongo.codec.JmongoRefCodecProvider
import com.google.inject.AbstractModule
import com.google.inject.Provides
import com.google.inject.Singleton
import com.mongodb.MongoClientSettings
import com.mongodb.ServerApi
import com.mongodb.ServerApiVersion
import com.mongodb.kotlin.client.coroutine.MongoClient
import io.github.classgraph.ClassGraph
import jakarta.persistence.Embeddable
import org.bson.codecs.configuration.CodecRegistries
import org.bson.codecs.configuration.CodecRegistry
import org.bson.codecs.pojo.PojoCodecProvider

abstract class MongoGuice(private val database: String, vararg packages: Class<*>) :
    AbstractModule() {
  companion object {
    val CACHE_SIZE by IxtfEnvLong("ixtf.mongo.cache.size", 10_000)
  }

  protected open val pojoCodecRegistries by lazy {
    PojoCodecProvider.builder().run {
      ClassGraph()
          .acceptPackages(*packages.map { it.packageName }.toTypedArray())
          .enableAllInfo()
          .scan()
          .use { scanResult ->
            scanResult.allClasses
                .asSequence()
                .filterNot {
                  it.isAbstract ||
                      it.isInterface ||
                      it.isAnnotation ||
                      it.isEnum ||
                      it.isPrivate ||
                      it.isSynthetic ||
                      it.isAnonymousInnerClass
                }
                .map { it.loadClass() }
                .filterNot { runCatching { it.kotlin.isData }.getOrDefault(false) }
                .filter {
                  MongoEntityBase::class.java.isAssignableFrom(it) ||
                      it.getAnnotation(Embeddable::class.java) != null
                }
                .distinct()
                .toList()
                .forEach { register(it) }
          }
      //          .use { scanResult ->
      //            scanResult.allClasses
      //                .asSequence()
      //                .filterNot {
      //                  it.isAbstract ||
      //                      it.isInterface ||
      //                      it.isAnnotation ||
      //                      it.isEnum ||
      //                      it.isPrivate ||
      //                      it.isSynthetic ||
      //                      it.isAnonymousInnerClass
      //                }
      //                .map { it.loadClass() }
      //                .filterNot {
      //                  val isData = runCatching { it.kotlin.isData }.getOrDefault(false)
      //                  isData && MongoEntityBase::class.java.isAssignableFrom(it).not()
      //                }
      //                .distinct()
      //                .forEach { register(it) }
      //          }
      CodecRegistries.fromProviders(build())
    }
  }

  @Singleton
  @Provides
  private fun CodecRegistry(): CodecRegistry =
      buildList {
            customerCodecRegistry()?.let { add(it) }
            add(pojoCodecRegistries)
            add(CodecRegistries.fromProviders(JmongoRefCodecProvider, IOperatorCodecProvider))
            add(MongoClientSettings.getDefaultCodecRegistry())
          }
          .let { CodecRegistries.fromRegistries(it) }

  @Singleton
  @Provides
  private fun MongoClient(codecRegistry: CodecRegistry): MongoClient =
      MongoClientSettings.builder().run {
        serverApi(ServerApi.builder().version(ServerApiVersion.V1).build())
        codecRegistry(codecRegistry)
        retryWrites(true)
        retryReads(true)
        build(this)
        MongoClient.create(build())
      }

  @Singleton @Provides private fun Jmongo(client: MongoClient) = Jmongo(client, database)

  protected abstract fun build(builder: MongoClientSettings.Builder)

  protected open fun customerCodecRegistry(): CodecRegistry? = null
}
